package ca.uguu.unknown_space.worldobjects.background;

import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.ArrayList;

import net.minidev.json.JSONValue;

import ca.uguu.gamelib.libgdx.concepts.PhysicsScene;
import ca.uguu.gamelib.libgdx.concepts.SpriteArea;
import ca.uguu.gamelib.libgdx.concepts.UnmanagedTextureEntity;
import ca.uguu.gamelib.libgdx.globals.Constants;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.Shape;

public abstract class Vessel extends UnmanagedTextureEntity{
	//Ships, turrets : things that have hulls, shields, weapons, engines (maybe)
	final private Body body;
	final private PhysicsScene scene;
	final private AbstractList<WeaponSlot> weaponSlots = new ArrayList<WeaponSlot>();
	final private AbstractList<Engine> engineSlots = new ArrayList<Engine>();
	
	private final Weapon[] weapons;
	
	public Vessel(PhysicsScene scene, float initialX, float initialY, float initialRotation, AbstractList<Weapon> weapons){
		//Weapons needs to be some sort of loadout descriptor, including amenities
		//Either that or it's a Fleet object, which contains shipdef objects, themselves describing the fleet's composition
		super();
		
		this.scene = scene;
		
		final BodyDef bd = new BodyDef();
		bd.type = BodyType.DynamicBody;
		bd.position.set(initialX, initialY);
		this.body = this.scene.getWorld().createBody(bd);
		this.rotate(initialRotation);
		this.assembleBody();
		
		this.weapons = new Weapon[this.weaponSlots.size()];
		this.bindWeapons(weapons);
		
		this.assembleSprite();
	}
	
	static private <T> AbstractList<T> unpackAreas(Class<T> areaType, AbstractList<Object> areas){
		AbstractList<T> spriteAreas = new ArrayList<T>(areas.size());
		for(Object area : areas){
			try{
				spriteAreas.add(areaType.getConstructor(AbstractMap.class).newInstance(area));
			}catch(Exception e){/*Can't do much*/}
		}
		return spriteAreas;
	}
	
	private void attachShape(SpriteArea area, int widthHalf, int heightHalf){
		final Shape shape;
		final Vector2 centre = area.getCentre(-widthHalf, -heightHalf);
		centre.y = -centre.y;
		if(area.width == area.height){
			final CircleShape circleShape = new CircleShape();
			shape = circleShape;
			circleShape.setPosition(centre);
			circleShape.setRadius(area.width / 2);
		}else{
			final PolygonShape polygonShape = new PolygonShape();
			shape = polygonShape;
			polygonShape.setAsBox(area.width / 2, area.height / 2, centre, 0);
		}
		FixtureDef fixtureDef = new FixtureDef();
		fixtureDef.shape = shape;
		this.body.createFixture(fixtureDef);
	}
	
	@SuppressWarnings("unchecked")
	protected void assembleBody(){
		final AbstractMap<String, Object> shipJson = (AbstractMap<String, Object>)JSONValue.parse(Gdx.files.internal("data/ships/debug/ship.json").read());
		final AbstractMap<String, Object> shipDimensions = (AbstractMap<String, Object>)shipJson.get("dimensions");
		final int widthHalf = ((Integer)(shipDimensions.get("width"))).intValue() / 2;
		final int heightHalf = ((Integer)(shipDimensions.get("height"))).intValue() / 2;
		
		final AbstractMap<String, Object> hitboxes = (AbstractMap<String, Object>)shipJson.get("hitboxes");
		for(SpriteArea area : Vessel.unpackAreas(SpriteArea.class, (AbstractList<Object>)(hitboxes.get("hull")))){
			this.attachShape(area, widthHalf, heightHalf);
		}
		for(SpriteArea area : Vessel.unpackAreas(SpriteArea.class, (AbstractList<Object>)(hitboxes.get("power")))){
			this.attachShape(area, widthHalf, heightHalf);
		}
		for(SpriteArea area : Vessel.unpackAreas(SpriteArea.class, (AbstractList<Object>)(hitboxes.get("shields")))){
			this.attachShape(area, widthHalf, heightHalf);
		}
		for(SpriteArea area : Vessel.unpackAreas(SpriteArea.class, (AbstractList<Object>)(hitboxes.get("sensors")))){
			this.attachShape(area, widthHalf, heightHalf);
		}
		
		for(WeaponSlot slot : Vessel.unpackAreas(WeaponSlot.class, (AbstractList<Object>)(shipJson.get("weapons")))){
			this.attachShape(slot, widthHalf, heightHalf);
			this.weaponSlots.add(slot);
		}
		for(Engine engine : Vessel.unpackAreas(Engine.class, (AbstractList<Object>)(shipJson.get("engines")))){
			this.attachShape(engine, widthHalf, heightHalf);
			this.engineSlots.add(engine);
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected void assembleSprite(){
		final AbstractMap<String, Object> shipJson = (AbstractMap<String, Object>)JSONValue.parse(Gdx.files.internal("data/ships/debug/ship.json").read());
		final Pixmap base = new Pixmap(Gdx.files.internal("data/ships/debug/base.png"));
		
		for(Weapon weapon : this.weapons){
			if(weapon != null){
				weapon.draw(base);
			}
		}
		
		//No engines, though, since those are rendered at runtime
		
		final AbstractMap<String, Object> paint = (AbstractMap<String, Object>)shipJson.get("paint");
		base.setColor(0.2f, 0.2f, 0.9f, 0.8f);
		for(SpriteArea area : Vessel.unpackAreas(SpriteArea.class, (AbstractList<Object>)(paint.get("base")))){
			base.fillRectangle(area.getLeft(), area.getTop(), area.width, area.height);
		}
		base.setColor(0.7f, 0.7f, 0, 0.8f);
		for(SpriteArea area : Vessel.unpackAreas(SpriteArea.class, (AbstractList<Object>)(paint.get("top")))){
			base.fillRectangle(area.getLeft(), area.getTop(), area.width, area.height);
		}
		
		final Pixmap shell = new Pixmap(Gdx.files.internal("data/ships/debug/shell.png"));
		base.drawPixmap(shell, 0, 0);
		shell.dispose();
		
		this.texture = new Texture(base);
		base.dispose();
		
		this.texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
		this.sprite = new Sprite(texture);
	}
	
	public WeaponSlot[] enumerateWeaponSlots(){
		return this.weaponSlots.toArray(new WeaponSlot[this.weaponSlots.size()]);
	}
	public void bindWeapons(AbstractList<Weapon> weapons){
		for(int i = 0; i < this.weapons.length; i++){
			if(i < weapons.size()){
				this.weapons[i] = weapons.get(i);
			}else{
				this.weapons[i] = null;
			}
		}
	}
	
	public float getVelocity(){
		final Vector2 velocity = this.body.getLinearVelocity();
		final float angle = this.body.getAngle();
		return (float)(((-velocity.x / Math.sin(angle)) + (velocity.y / Math.cos(angle))) / 2.0);
	}
	public Vector2 getVelocityVector(){
		return this.body.getLinearVelocity();
	}
	public void setVelocity(float velocity){
		final float angle = this.body.getAngle();
		this.body.setLinearVelocity((float)Math.sin(angle) * -velocity, (float)Math.cos(angle) * velocity);
	}
	
	public float getMovementDamping(){
		return this.body.getLinearDamping();
	}
	public void setMovementDamping(float damping){
		this.body.setLinearDamping(damping);
	}
	
	public void rotate(float degrees){
		float rotation = ((degrees * Constants.DEGREES_TO_RADIANS) + this.body.getAngle());
		if(rotation >= Constants.CIRCLE_RADIANS){
			rotation -= Constants.CIRCLE_RADIANS;
		}
		this.body.setTransform(this.body.getPosition(), rotation);
	}
	
	//TODO: Should have a 'tick' method that can handle things like
	//acceleration and repairs
	//This also means there should be an accelerateTo(speed) method, which
	//allows local attributes like mass and engine power to calculate the
	//curve
	
	public Sprite getSprite(){
		this.sprite.setRotation(this.body.getAngle() * Constants.RADIANS_TO_DEGREES);
		Vector2 pos = this.body.getPosition();
		this.sprite.setPosition(pos.x - (this.sprite.getWidth() / 2), pos.y - (this.sprite.getHeight() / 2));
		return this.sprite;
	}
}
